#include<stdio.h>

/*
				tips:有心情就看吧，没心情的时候就看最底下的入门示例代码吧


------------------------------------------------------------------------宏--------------------------------------------------------------------------------------------

	在C语言中，常量是使用频率很高的一个量。常量是指在程序运行过程中，其值不能被改变的量。常量常使用#define来定义。
	使用#define定义的常量也称为符号常量，可以提高程序的运行效率。
	
	基本格式：
		
		#define 宏名 替换列表

		·	#define：预处理器指令，用于定义宏
		·	宏名：一个标识符，用于表示要替换的内容。
		·	替换列表：宏名被替换为的内容，可以是常量、表达式或其他代码片段。

	其常见的用法包括两种，分别是：
		
		1.无参宏定义：
			·无参宏定义不带参数，仅用于常量替换。
			·示例：
				#define PI 3.14159

		2.带参宏定义：
			·带参宏定义允许在宏中使用参数，类似于函数调用。
			·示例：
				#define SQUARE(x) ((x) * (x))



-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

																	什么是宏定义

	  计算机科学里的宏是一种抽象的,根据一系列预定义的规则进行文本替换。
	  宏是一种将一系列命令组织在一起，作为一个单独命令来完成特定任务的方式。在编译语言中，宏展开是在编译时进行的，编译器会自动将宏替换为一系列指令。宏展开器是用于执行宏展开的工具。

	  宏定义的实质
	  再牛逼的宏定义，说到底也是机械替换。(好比如一个写很多个标签叫 A ,帖子指定的代码行)


	  C 语言三大预处理功能
	  宏定义；文件包含；条件编译。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
		
																	宏定义的作用
	 1.简化代码：
		·针对重复出现的常量或表达式，使用宏定义可以减少冗余代码，提高可读性。
		 例如，将圆周率定义为 #define PI 3.14159，便于后续使用
		·示例：
				#define PI 3.14159
				double area = PI * radius * radius;

	 2.增强可读性：
		·使用有意义的宏名可以提高代码的可读性和易维护性。
		·例如：
			#define MAX(a, b) ((a) > (b) ? (a) : (b)) 让比较大小的逻辑更加清晰。

	 3.条件编译：
		·宏定义结合条件编译指令（如 #ifdef、#ifndef、#if）可以实现不同条件下的代码生成，适用于多平台开发或调试信息的控制。
		·实例：
			#ifdef DEBUG
			#define LOG(msg) printf("[DEBUG] %s\n", msg)
			#else
			#define LOG(msg)
			#endif

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

																	宏定义的特点

		1.无类型检查：
		  ·宏定义属于纯文本替换，不进行类型检查。因此可能存在安全隐患，例如：

				#define SQUARE(x) x * x
				int result = SQUARE(2 + 3); // 展开为：2 + 3 * 2 + 3，结果为 11，而非 25。

		  ·为了避免优先级问题，需要手动添加括号：
				#define SQUARE(x) ((x) * (x))
				
		2.执行速度快：

			·宏的执行速度快于函数调用，因为无需函数调用的栈操作（压入栈、参数传递、返回等），直接进行文本替换。
		
		3.不受作用域限制：

			·宏定义的作用域是它是无作用域限制的，即一旦定义，其作用将贯穿整个文件，直到被取消定义（使用 #undef）。
		
		4.字符串化和连接符：

			1).	# 运算符可以将宏参数转换为字符串，例如：
				
				#define STR(x) #x
				printf("%s\n", STR(hello)); // 输出：hello

			2).	## 运算符可以将两个标识符连接成一个，例如：
				
				#define CONCAT(x, y) x##y
				int xy = CONCAT(x, y); // 替换为 int xy = xy;

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
		
																		注意事项

		1.使用括号保护优先级：
			·在带参宏定义中，需要为参数添加括号，以避免运算优先级问题：
				#define SQUARE(x) ((x) * (x))



		2.避免重复定义：
			·为了避免多次定义导致的错误，可以结合 #ifndef 和 #define：
				#ifndef PI
				#define PI 3.14159
				#endif
		
		3.合理使用条件编译：
			使用 #ifdef、#ifndef 和 #if 实现条件编译，避免不必要的代码生成：
			#ifdef DEBUG
			#define LOG(msg) printf("[DEBUG] %s\n", msg)
			#else
			#define LOG(msg)
			#endif

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
																	宏定义和函数的区别

			省流：虽然宏定义也有所谓的”形参“和”实参“，但在宏定义的过程中，并不需要为形参指定类型。这是因为宏定义只是进行机械替换，并不需要为参数分配内存空间。
				 而函数不同，在函数中形参和实参是两个不同的变量，都有自己的内存空间和作用域，调用时是要把实参的值传递给形参。

				 ------------------------------------------------------------------------------------------------------------
				 |		属性			|					#define 宏					|				  函数					|
				 ------------------------------------------------------------------------------------------------------------
				 |					|												|										|
				 |					|		每次调用时，宏代码都会被插入到程序中。		|		函数代码只出现在一个地方，			|	
				 |		代码长度		|		除了非常小的宏之外，程序的长度将大幅增加	|		每次调用这个函数时，				|
				 |					|												|		调用的都是那个地方的同一份代码		|
				 |					|												|										|
				 ------------------------------------------------------------------------------------------------------------
				 |					|												|										|
				 |		执行速度		|					 更快						|		存在函数调用/返回的而外的开销		|
				 |					|												|										|
				 ------------------------------------------------------------------------------------------------------------
				 |					|												|										|
				 |					|		宏参数的求值是在所有周围下上文环境里，		|		函数参数只在函数调用时求值一次，	|
				 |	  操作符优先级	|		除非给它们加上括号，否则邻近操作符的优先级，|		它的结果值传递给函数。			|
				 |					|		可能会产生不可预测的结果					|		表达式的求值更容易预测。			|
				 |					|												|										|
				 ------------------------------------------------------------------------------------------------------------
				 |					|												|										|
				 |					|		参数每次用于宏定义时。					|	    参数在函数被调用前只求值一次，		|
				 |					|		它们都将重新求值，由于多次求值，			|	    在函数中多次使用参数，			|
				 |	   参数求值		|		具有副作用的参数可能会产生不可预料的结果。	|	    并不会导致多种求值过程，			|
				 |					|												|	    参数的副作用并不会造成			|
				 |					|												|		任何特殊问题						|
				 |					|												|										|
				 ------------------------------------------------------------------------------------------------------------
				 |					|												|										|
				 |	   				|												|	    函数的参数是与类型有关的，			|
				 |	   参数类型		|		宏与类型无关，只要参数的操作是合法的，		|	    如果参数的类型不同,				|
				 |					|		它可以适用于任何参数类型					|	    就需要使用不同的函数，			|
				 |					|												|	    即使它们的任务是相同的			|
				 |					|												|										|
				 ------------------------------------------------------------------------------------------------------------
											
											
											



		详细原理：

		1.定义方式和实现原理
				宏定义：

					·使用 #define 指令在编译前进行定义。
					 预处理器在编译阶段将宏调用替换为宏定义的内容，本质上是一种文本替换机制。
					 无类型检查，预处理器仅进行简单的文本匹配和替换
				
				函数：

					·使用函数声明和函数定义在编译时生成机器码。
					 函数调用时，编译器会在运行时执行相应的代码，涉及参数传递、栈操作等。
					 有严格的类型检查，编译器会检查参数和返回值的类型是否匹配

		
		2.执行时间和效率

			宏定义：

				·在编译阶段进行文本替换，不占用运行时间。
				 执行速度快，因为不涉及函数调用的堆栈操作（如参数压入栈、函数返回等）。
				 但可能导致代码膨胀，因为每次调用宏都会生成相应的代码段

			函数：

				·在程序运行时执行，涉及参数传递、栈操作等，因此开销较大。
				 如果编译器进行了内联优化，可能会将简单的函数调用直接展开，从而接近宏的执行效率


		3. 参数处理方式
			
			宏定义：
				无形式参与计算，参数直接替换到宏定义中。
				无类型检查，可能导致类型安全问题。
				参数替换时需注意运算优先级问题。例如：
					
					#define SQUARE(x) x * x
					int result = SQUARE(3 + 2); // 展开为：3 + 2 * 3 + 2，结果为 11，而非预期的 25。

				这要求在宏定义中使用括号来正确处理优先级：
					
					#define SQUARE(x) ((x) * (x))

			函数：
				函数参数需要明确声明数据类型。
				函数调用时，参数会被压入栈中传递，编译器自动管理运算优先级。
				支持递归调用和调试，但由于参数传递的开销，复杂参数可能导致性能下降
		
		4. 适用场景和用途
			
			宏定义:
				适合用于简单、短小的代码片段，通常用于重复代码的简化或条件编译。
				可以应用于类型无关的场景，特别适合处理需要同时适用于多种数据类型的代码。
				缺点是缺乏类型检查，可能导致错误或逻辑混乱，尤其是在复杂的场景中。

			函数:
				适合用于复杂的逻辑和需要类型检查的场景。
				支持递归调用，并且可以使用调试器逐步执行。
				函数具有明确的作用域，代码组织和维护性更好，适合复用和模块化开发.

		5.主要优缺点

			宏定义的优点:

				·执行速度快：不涉及函数调用的堆栈操作。
				·代码生成高效：适用于简单、重复的代码片段。
				·类型无关：可以用于不同数据类型，无需为每种类型定义独立的宏。
				·调试时可直接查看替换后的代码。

			宏定义的缺点：
				
				缺乏类型检查：可能导致类型安全问题。
				运算优先级问题：需要手动处理括号以避免错误。
				代码膨胀：每次调用宏都会生成代码，可能导致程序体积增大。
				复杂场景下可读性差：逻辑复杂时，维护难度高。

			函数的优点：
				
				支持复杂逻辑：可以处理复杂的参数传递和控制流程。
				类型检查严格：提高代码的安全性和可靠性。
				支持递归调用和调试：调试工具可以逐步执行函数调用。

			函数的缺点：
				
				执行效率相对较低：涉及参数传递和栈操作。
				代码量可能增加：复杂的函数调用可能导致代码膨胀。
				类型不通用：需要为不同数据类型定义多个函数或使用重载。
				
*/


//1.定义常量
//利用宏定义定义常量

//#define 可以用来定义常量，避免在代码中直接使用硬编码的数字或字符串。这种方式不仅可以提高代码的可读性，还便于集中管理。
//示例：定义数学常量实现求出圆的面积

#define PI 3.1415926

void test1()
{
	int r = 5;
	double s = PI * r * r;
	printf("s = %.2f", s);

	//优点：便于维护，修改常量只需修改定义。
	//注意：#define 的常量不会进行类型检查，调用时需确保其使用正确。
}




//2.定义简单函数
//#define 可以模拟简单的函数，避免函数调用的开销，但需注意类型检查和运算优先级问题。
//实例：计算平方<数学表达式>

#define SQUARE(x) ((x) * (x)) //千万要注意优先级考虑!!!
void test2()
{

	int result = SQUARE(5 + 3); // 注意：需要使用括号保护优先级
	printf("Square = %d\n", result); // 输出：Square = 64

	//优点：执行速度快，适合简单计算。
	//注意：宏缺乏类型检查，运算优先级问题可能引起错误。
}



//3.带参数的复杂宏
//#define 可以定义带参数的宏，但需小心处理括号以避免错误。
//实例：判断最大值
#define MAX(a, b) ((a) > (b) ? (a) : (b))
void test3()
{
	int x = 10, y = 20;
	printf("Max = %d\n", MAX(x, y)); // 输出：Max = 20

	double a = 3.14, b = 2.71;
	printf("Max = %.2f\n", MAX(a, b)); // 输出：Max = 3.14

	//优点：通用性强，支持多种数据类型。
	//注意：参数替换时需注意括号使用，避免运算优先级错误
}

//4. 条件编译<基本用不上>
//#define 可以结合条件编译指令（如 #ifdef、#ifndef、#if 等）实现不同的代码逻辑。

#ifdef  _DEBUG  //左上角有一个Debug标识,_DEBUG标识进入调试模式
#define LOG(msg) printf("[DEBUG] %s\n", msg)

#else
#define LOG(msg)

#endif

void test4()
{
	LOG("This is a debug message"); // 输出 "[DEBUG] This is a debug message"（仅在调试模式下）
}

//5.字符串化和连接符
//#define支持特殊符号 # 和 ## ,分别用于字符串化和连接符操作
//示例：字符串化
#define STR(x) # x

void test5_1()
{
	printf("%s\n", STR(hello));
}

//示例：连接符
#define CONCAT(x,y) x ## y

void test5_2()
{

	printf("%d\n", CONCAT(5,20));

}

//优点：支持动态生成标识符和字符串。
//注意：使用时需小心语法规范。

//6.多行宏定义
//#define 支持定义多行宏，但需在行尾使用反斜杠（\）表示换行。
//示例：多行宏定义
#include<stdlib.h>

#define SAFE_FREE(ptr)\
if (ptr) {\
	free(ptr);\
	ptr = NULL;\
}

void test6()
{
	char* str = (char*)malloc(100);
	SAFE_FREE(str);
	if (str == NULL) printf("动态内存已经释放");
	//优点：增强代码的可读性和复用性。
	//注意：确保每行的反斜杠正确使用。

}  

//7.宏定义可变参数
//#define SHOWLIST(...) printf(#__VA_ARGS__) ,其中...表示使用可变参数,__VA_ARGS__在预处理中被实际的参数集所替代

#define SHOWLIST(...) printf(#__VA_ARGS__)
void test7_1()
{
	SHOWLIST(FishC, 520, 3.14\n);
}

#define PRINT(format,...) printf(# format,##__VA_ARGS__)

void test7_2()
{
	PRINT(num = % d\n, 520);
	PRINT(HELLO WORLD\n);
}



int main()
{
	test7_2();
	return 0;
}